package com.reyco1.media.audio
{
	import com.greensock.TweenMax;
	import com.greensock.easing.Linear;
	import com.reyco1.events.SmartEventDispatcher;
	import com.reyco1.interfaces.IPausable;
	import com.reyco1.util.ClassUtil;
	import com.reyco1.util.Delegate;
	import com.reyco1.util.MathUtil;
	
	import flash.events.Event;
	import flash.events.IOErrorEvent;
	import flash.events.ProgressEvent;
	import flash.events.TimerEvent;
	import flash.media.Sound;
	import flash.media.SoundChannel;
	import flash.media.SoundLoaderContext;
	import flash.media.SoundTransform;
	import flash.net.URLRequest;
	import flash.utils.Timer;
	
	public class CoreSound extends SmartEventDispatcher implements IPausable
	{
		private var sound:Sound;
		private var pausePosition:int;
		private var currentPosition:int;
		private var currentSoundTransform:SoundTransform;
		private var currentLoops:int;
		private var initialStartTime:int;
		private var volumeTween:TweenMax;
		private var progressTimer:Timer;
		private var bufferTime:Number;
		private var checkPolicyFile:Boolean;
		
		public  var id:String;
		public  var onLoadProgress:Function;
		public  var onLoadProgressParams:Array;
		public  var onLoadComplete:Function;
		public  var onLoadCompleteParams:Array;
		public  var onLoadError:Function;
		public  var onLoadErrorParams:Array;
		public  var onProgress:Function;
		public  var onProgressParams:Array;
		public  var onComplete:Function;
		public  var onCompleteParams:Array;
		public  var onStart:Function;
		public  var onStartParams:Array;
		public  var startOffset:int;
		public  var delay:int;
		public  var details:Object;
		public  var duration:int;
		public  var progress:int;
		public  var progressRatio:Number;
		public  var loadPercent:Number;
		public  var pauseSequenceOnComplete:Boolean;
		
		private var _isPlaying:Boolean;
		private var _isPaused:Boolean;
		private var _isStopped:Boolean
		private var _soundChannel:SoundChannel;
		private var _volume:Number;
		private var _isLibSound:Boolean;
		
		public function CoreSound(soundObject:*, properties:Object = null)
		{
			parseProperties( properties );
			determineSound( soundObject );
			
			progressTimer = new Timer(10);
			progressTimer.addEventListener(TimerEvent.TIMER, checkProgress);
		}
		
		public function play(startTime:int = 0, loops:int = 0, soundTransform:SoundTransform = null):void
		{
			if(!_isPaused)
			{
				initialStartTime = startTime == 0 ? startOffset == 0 ? 0 : startOffset : startTime;
				currentSoundTransform = soundTransform == null ? new SoundTransform( _volume ) : soundTransform;
				currentLoops = loops;
				
				_soundChannel = sound.play(initialStartTime, currentLoops, currentSoundTransform);
				_soundChannel.addEventListener(Event.SOUND_COMPLETE, handlComplete);
				
				startProgressCheck();
				
				_isPaused  = false;
				_isPlaying = true;
				_isStopped = false;
				
				log("now playing: " + id);
				
				if(onStart != null)
					Delegate.execute(onStart, onStartParams);
			}
			else
			{
				resume();
			}
		}
		
		public function pause():void
		{
			if(!_isStopped && !_isPaused && _isPlaying && _soundChannel)
			{
				stopProgressCheck();
				
				pausePosition = _soundChannel.position;
				_soundChannel.stop();
				_soundChannel.removeEventListener(Event.SOUND_COMPLETE, handlComplete);
				
				_isPaused  = true;
				_isPlaying = false;
				_isStopped = false;
			}
		}
		
		public function resume():void
		{
			if(!_isStopped && _isPaused && !_isPlaying && _soundChannel)
			{
				_soundChannel = sound.play(pausePosition, currentLoops, currentSoundTransform);
				_soundChannel.addEventListener(Event.SOUND_COMPLETE, handlComplete);
				
				startProgressCheck();
				
				_isPaused  	  = false;
				_isPlaying    = true;
				_isStopped	  = false;
			}
		}
		
		public function stop():void
		{
			stopProgressCheck();
			
			pausePosition = initialStartTime;
			if(_soundChannel)
				_soundChannel.stop();
			
			_isPaused  	  = false;
			_isPlaying    = false;
			_isStopped	  = true;
		}
		
		public function clear():void
		{
			if(progressTimer)
			{
				progressTimer.stop();
				progressTimer.addEventListener(TimerEvent.TIMER, checkProgress);
				progressTimer = null;
			}
			
			onComplete 		 		= null;
			onCompleteParams 		= null;
			onStart 		 		= null;
			onStartParams 	 		= null;
			onProgress 		 		= null;
			onProgressParams 		= null;
			onLoadProgress	 		= null;
			onLoadProgressParams	= null;
			onLoadComplete	 		= null;
			onLoadCompleteParams	= null;
			onLoadError		 		= null;
			onLoadErrorParams		= null;
			
			if(_soundChannel)
			{
				_soundChannel.stop();
				_soundChannel.removeEventListener(Event.SOUND_COMPLETE, handlComplete);
			}
			_soundChannel = null;
			
			details = null;
			
			if(volumeTween)
				volumeTween.kill();
			volumeTween = null;
		}
		
		private function parseProperties(properties:Object):void
		{
			properties 		 		= properties == null ? {} : properties;
			
			onLoadProgress	 		= properties.onLoadProgress				|| null;
			onLoadProgressParams	= properties.onLoadProgressParams 		|| null;
			onLoadComplete	 		= properties.onLoadComplete				|| null;
			onLoadCompleteParams	= properties.onLoadCompleteParams 		|| null;
			onLoadError		 		= properties.onLoadComplete				|| null;
			onLoadErrorParams		= properties.onLoadCompleteParams 		|| null;
			onComplete 		 		= properties.onComplete					|| null;
			onCompleteParams 		= properties.onCompleteParams			|| null;
			onProgress 		 		= properties.onProgress					|| null;
			onProgressParams 		= properties.onProgressParams			|| null;
			onStart 		 		= properties.onStart					|| null;
			onStartParams 	 		= properties.onStartParams				|| null;
			startOffset		 		= properties.startOffset				|| 0;
			delay			 		= properties.delay						|| 0;
			bufferTime		 		= properties.bufferTime					|| 1000;
			checkPolicyFile	 		= properties.bufferTime					|| false;
			id				 		= properties.id							|| "sound_" + MathUtil.random(5000, 1000);
			pauseSequenceOnComplete = properties.pauseSequenceOnComplete 	|| false;
			_volume	 		 		= properties.presetVolume				|| 1;
			_isLibSound		 		= properties.isLibSound					|| false;
			_isPlaying 		 		= false;
			_isPaused  		 		= false;	
			_isStopped 		 		= true;
			
			details	= {};
		}
		
		private function determineSound(soundObject:*):void
		{
			if(soundObject is String)
			{
				if(_isLibSound)
				{
					sound = ClassUtil.getSound( soundObject );
				}
				else
				{
					loadExternalSound(soundObject);
				}
			}
			else if(soundObject is Sound)
			{
				sound = soundObject as Sound;
			}
			else
			{
				log("The provided sound object must be either a linkage id to the sound in the library or an instance of the Sound class", "fatal");
			}
			
			duration = sound.length;
		}
		
		private function loadExternalSound(soundObject:*):void
		{
			sound = new Sound(new URLRequest(soundObject), new SoundLoaderContext(bufferTime, checkPolicyFile));
			sound.addEventListener(IOErrorEvent.IO_ERROR, handleSoundLoadError);
			sound.addEventListener(ProgressEvent.PROGRESS, handleSoundLoadProgress);
			sound.addEventListener(Event.COMPLETE, handleSoundLoadComplete);
		}
		
		protected function handleSoundLoadComplete(event:Event):void
		{
			sound.removeEventListener(IOErrorEvent.IO_ERROR, handleSoundLoadError);
			sound.removeEventListener(ProgressEvent.PROGRESS, handleSoundLoadProgress);
			sound.removeEventListener(Event.COMPLETE, handleSoundLoadComplete);
			
			if(onLoadComplete != null)
				Delegate.execute(onLoadComplete, onLoadCompleteParams);
		}
		
		protected function handleSoundLoadProgress(event:ProgressEvent):void
		{
			loadPercent = Math.round(100 * (event.bytesLoaded / event.bytesTotal));
			duration 	= (sound && sound.length > 0) ? ((sound.bytesTotal / (sound.bytesLoaded / sound.length))) : 0;
			
			if(onLoadProgress != null)
				Delegate.execute(onLoadProgress, onLoadProgressParams);
		}
		
		protected function handleSoundLoadError(event:IOErrorEvent):void
		{
			if(onLoadError != null)
				Delegate.execute(onLoadError, onLoadErrorParams);
		}
		
		private function handlComplete(event:Event):void
		{
			_isPaused  = false;
			_isPlaying = false;
			_isStopped = true;
			
			stopProgressCheck();
			
			_soundChannel = null;
			
			if(onComplete != null)
				Delegate.execute(onComplete, onCompleteParams);
			
			event.stopPropagation();
			
			dispatchEvent(new Event(Event.SOUND_COMPLETE, true));
		}
		
		private function startProgressCheck():void
		{
			progressTimer.start();
		}
		
		private function stopProgressCheck():void
		{
			progressTimer.stop();
		}
		
		private function checkProgress(event:TimerEvent):void
		{
			progress = _soundChannel.position;
			progressRatio = progress / duration;
			
			dispatchEvent( new ProgressEvent(ProgressEvent.PROGRESS, true) );
			
			if(onProgress != null)
				Delegate.execute(onProgress, onProgressParams);
		}
		
		public function fadeVolume(vol:Number, time:Number = 1, onFadeComplete:Function = null):void
		{
			if(_soundChannel)
				volumeTween = TweenMax.to(soundChannel, time, { ease:Linear.easeNone, volume:vol, onComplete:onFadeComplete });
		}
		
		public function get isPlaying():Boolean
		{
			return _isPlaying;
		}
		
		public function get isPaused():Boolean
		{
			return _isPaused;
		}
		
		public function get soundChannel():SoundChannel
		{
			return _soundChannel;
		}
		
		public function get volume():Number
		{
			return _volume;
		}
		
		public function set volume(value:Number):void
		{
			_volume = value;
			if(_soundChannel)
				_soundChannel.soundTransform = new SoundTransform ( _volume );
		}
		
		public function get isStopped():Boolean
		{
			return _isStopped;
		}
		
		public function set isStopped(value:Boolean):void
		{
			_isStopped = value;
		}		
	}
}